﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization;

namespace MonsterNet
{
	[Serializable]
    public class MNSynchronisedDirectory : MNSynchronisedFile, ISerializable {
        public const string kRootSyncDirectory = @"\\ROOT//";
		
		protected delegate void ChildUpdateEvent(MNChildUpdateEventArgs e);
		protected event ChildUpdateEvent RaiseChildUpdateEvent;	
		
        #region Variables

        private SerializableDictionary<string, MNSynchronisedFile> _files;
        private SerializableDictionary<string, MNSynchronisedDirectory> _directories;
				
        #endregion

        #region Getters/Setters
        public new string Name
        {
            get 
            {   //if this is the root directory, then return directory separator 
                if (_name == kRootSyncDirectory)
                {   return Path.DirectorySeparatorChar.ToString(); }
                else
                {   return _name; } 
            }
        }	

		public SerializableDictionary<string, MNSynchronisedFile> Files
        {
            get 
			{ 
				return _files; 
			}
        }
		
        public SerializableDictionary<string, MNSynchronisedDirectory> Directories
        {
            get 
			{ 
				return _directories; 
			}
        }
		
		/// <summary>
		/// Get the total size of this directory including all files and sub-directory files. 
		/// </summary>
		/// <param name="location">
		/// A <see cref="MNSynchronisedFSObjectLocation"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Double"/> which is the size in bytes.
		/// </returns>
        public new ulong GetSizeInBytesForLocation(MNFSObjectLocation location)
        {
            ulong total = 0;
            foreach (KeyValuePair<string, MNSynchronisedFile> kvp in _files)
            {
                if (kvp.Value.GetExistsAtForLocation(location))
                { total += kvp.Value.GetSizeInBytesForLocation(location); }
            }
            foreach (KeyValuePair<string, MNSynchronisedDirectory> kvp in _directories)
            {
                if (kvp.Value.GetExistsAtForLocation(location))
                { total += kvp.Value.GetSizeInBytesForLocation(location); }
            }
            return total;
        }
		
		/// <summary>
		/// Return a human readable string representing the size of the directory.
		/// </summary>
		/// <param name="location">
		/// A <see cref="MNSynchronisedFSObjectLocation"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.String"/>
		/// </returns>
		public new string GetSizeAsStringForLocation(MNFSObjectLocation location)
		{
			return MNGlobal.GetSizeFromUlongToString(this.GetSizeInBytesForLocation(location));
		}
		
		/// <summary>
		/// Return the directories full path. 
		/// </summary>
		/// <param name="rootPath">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.String"/>
		/// </returns>
        public string GetFullPathWithRootPath(string rootPath)
        {
            if (_name == kRootSyncDirectory)
            { return rootPath; }
            else
            { return Path.Combine(rootPath, _rootlessPath); }
        }
		
		/// <summary>
		/// Overrides the MNSynchronisedFSObject private setter to set all of this directories children to private.
		/// </summary>
		public new bool Private {
			get {
				return this._private;
			}
			set {
				_private = value;
				//files 
				foreach(KeyValuePair<string, MNSynchronisedFile> kvp in _files) {
					kvp.Value.Private = _private;
				}
				//directories
				foreach(KeyValuePair<string, MNSynchronisedDirectory> kvp in _directories) {
					kvp.Value.Private = _private;
				}	
			}
		}
		
		/// <summary>
		/// Returns true is all of this directorys contents exists at both the source and the destination. 
		/// </summary>
		public bool ChildrenInSync {
			get {
				bool insync = true;
				
				if(_files.Count == 0 && _directories.Count == 0) {
					// no children
					return false;
				} else {
					
					foreach (KeyValuePair<string, MNSynchronisedFile> kvp in _files) {
						if(kvp.Value.GetExistsAtForLocation(MNFSObjectLocation.Destination) == false 
						   || kvp.Value.GetExistsAtForLocation(MNFSObjectLocation.Source) == false) {
							insync = false;
							break;
						}
					}
					
					if(insync) {
						foreach (KeyValuePair<string, MNSynchronisedDirectory> kvp in _directories) {
							if(!kvp.Value.ChildrenInSync) {
								insync = false;
								break;
							}						
						}
					}
				}
				return insync;
			}
		}
		
        #endregion

        #region Constructors
		
		public MNSynchronisedDirectory() : base ()
		{
            _directories = new SerializableDictionary<string,MNSynchronisedDirectory>();
            _files = new SerializableDictionary<string,MNSynchronisedFile>();	
		}
		
        public MNSynchronisedDirectory(string newName) : this()
		{
			_name = newName;
            _rootlessPath = this.Name;
			if(newName == kRootSyncDirectory) {
				_existsAt[(int)MNFSObjectLocation.Destination] = true;
				_existsAt[(int)MNFSObjectLocation.Portable] = true;
				_existsAt[(int)MNFSObjectLocation.Source] = true;
			}
		}

        public MNSynchronisedDirectory(DirectoryInfo newDir, string rootPathname, MNFSObjectLocation location, DateTime foundDateTime) : this(newDir.Name)
		{
            _rootlessPath = MNGlobal.CreateRootlessPath(newDir.FullName, rootPathname);
            _dateFound[(int)location] = foundDateTime;
			_dateModified[(int)location] = newDir.LastWriteTime;
            _existsAt[(int)location] = true;
        }
		
        #endregion
		
		#region Serialization
		private const string kSerialiseFiles = "f";
		private const string kSerialiseDirectories = "d";
		
		public MNSynchronisedDirectory(SerializationInfo info, StreamingContext ctxt) : base (info, ctxt) {	
			_files = (SerializableDictionary<string, MNSynchronisedFile>)info.GetValue(kSerialiseFiles,typeof(Dictionary<string, MNSynchronisedFile>));
			_directories = (SerializableDictionary<string, MNSynchronisedDirectory>)info.GetValue(kSerialiseDirectories, typeof(Dictionary<string, MNSynchronisedDirectory>));	
		}
		
		public new void GetObjectData(SerializationInfo info, StreamingContext ctxt) {
			base.GetObjectData(info, ctxt);
		
			info.AddValue(kSerialiseFiles, _files);
			info.AddValue(kSerialiseDirectories, _directories);		
		}		
		
		/// <summary>
		/// Call once serialization is complete to setup event handlers 
		/// </summary>
		public void SubscribeToChildEvents() {
			foreach(KeyValuePair<string, MNSynchronisedDirectory> kvp in Directories) {
				//subscribe this dir to its childs events
				kvp.Value.SubscribeChildUpdate(OnChildUpdateEvent);
				//subscribe this child to it's childrens events
				kvp.Value.SubscribeToChildEvents();
			}
		}

		#endregion		
		
		#region maintenence
		
        private void Update(DirectoryInfo dir, MNFSObjectLocation location, DateTime scanDateTime)  {
            _dateModified[(int)location] = dir.LastWriteTime;
            _existsAt[(int)location] = true;
        }
		
		/// <summary>
		/// Update or add a new directory to this directory
		/// </summary>
		/// <param name="directory">
		/// A <see cref="DirectoryInfo"/> representing the directory to be updated/inserted
		/// </param>
		/// <param name="rootPath">
		/// A <see cref="System.String"/> the root path of the directory being updated/inserted
		/// </param>
		/// <param name="location">
		/// A <see cref="MNFSObjectLocation"/> the location of the the directory being updated/inserted
		/// </param>
		/// <param name="scanDateTime">
		/// A <see cref="DateTime"/> the current scanning datetime
		/// </param>
		/// <returns>
		/// A <see cref="MNSynchronisedDirectory"/> representing the updated/inserted directory
		/// </returns>
        public MNSynchronisedDirectory SetChildDirectory(DirectoryInfo directory, string rootPath, 
		                                                 MNFSObjectLocation location, DateTime scanDateTime, out bool newDirectory) {
			
			newDirectory = false;
            if (_directories.ContainsKey(directory.Name))
            {
				// directory already exists
                _directories[directory.Name].Update(directory, location, scanDateTime);
                return _directories[directory.Name];
            }
            else
            {
				// newly found directory
                MNSynchronisedDirectory syncDir = new MNSynchronisedDirectory(directory, rootPath, location, scanDateTime);
				syncDir.SubscribeChildUpdate(OnChildUpdateEvent);
                _directories.Add(directory.Name, syncDir);
				DoRaiseChildUpdateEvent(location, scanDateTime);
				newDirectory = true;
                return syncDir;
            }
        }
		
		/// <summary>
		/// Update or insert a new file to the directory 
		/// </summary>
		/// <param name="file">
		/// A <see cref="FileInfo"/>
		/// </param>
		/// <param name="rootPath">
		/// A <see cref="System.String"/>
		/// </param>
		/// <param name="location">
		/// A <see cref="MNFSObjectLocation"/>
		/// </param>
		/// <param name="scanDateTime">
		/// A <see cref="DateTime"/>
		/// </param>
		/// <returns>
		/// A <see cref="MNSynchronisedFile"/>
		/// </returns>
        public MNSynchronisedFile SetChildFile(FileInfo file, string rootPath, MNFSObjectLocation location, DateTime scanDateTime, out bool newFile) {
			newFile = false;
			//dont add OSX system files, ._* and .ds_store 
			if(!(file.Name.StartsWith(@"._") || file.Name == @".DS_Store")) {
				if(_files.ContainsKey(file.Name)) {
	                _files[file.Name].Update(file, location, scanDateTime);
	                return _files[file.Name];
				} else {	
					//newly found file
                	MNSynchronisedFile newSyncFile = 
						new MNSynchronisedFile(file, rootPath, 
						                       location, scanDateTime, _private);
                	_files.Add(file.Name, newSyncFile);
					
					_dateFound[(int)location] = scanDateTime;// update this directories dateFound 
					DoRaiseChildUpdateEvent(location, scanDateTime); 
					newFile = true;
                	return newSyncFile;
				}				
			} else {
					return new MNSynchronisedFile();
			}		
    	}
	
		/// <summary>
		/// Call to reset the current location's files.
		/// Set all files to not exist at current location and portable path.
		/// </summary>
		/// <param name="location">
		/// A <see cref="MNFSObjectLocation"/>
		/// </param>
		public void ResetExistsAtForLocation(MNFSObjectLocation location) {
			//all files in this path
			foreach(KeyValuePair<string, MNSynchronisedFile> kvp in _files) {
				kvp.Value.SetExistsAtForLocation(location, false);
				kvp.Value.SetExistsAtForLocation(MNFSObjectLocation.Portable, false);				
			}
			
			//recurse into each directory
			foreach(KeyValuePair<string, MNSynchronisedDirectory> kvp in _directories) {
				kvp.Value.SetExistsAtForLocation(location, false);
				kvp.Value.SetExistsAtForLocation(MNFSObjectLocation.Portable, false);
				kvp.Value.ResetExistsAtForLocation(location);
			}			
		}
		
		/// <summary>
		/// Search this directory for directories and files that contain the searchString 
		/// </summary>
		/// <param name="searchString">
		/// A <see cref="System.String"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Boolean"/>
		/// </returns>
		public bool ChildrenSearch(string searchString) {
			bool found = false;
			
			// searchs files
			foreach (KeyValuePair<string, MNSynchronisedFile> kvp in _files) {
				if(kvp.Value.Name.ToUpper().IndexOf(searchString) > -1) {
					found = true;
					break;
				}
			}
			
			// search directory names
			if(!found) {
				foreach (KeyValuePair<string, MNSynchronisedDirectory> kvp in _directories) {
					if(kvp.Value.Name.ToUpper().IndexOf(searchString) > -1) {
						found = true;
						break;
					}
				}
			}
			
			//keep searching directory contents
			if(!found) {
				foreach (KeyValuePair<string, MNSynchronisedDirectory> kvp in _directories) {
					if(kvp.Value.ChildrenSearch(searchString)) {
						found = true;
						break;
					}
				}
			}
			
			return found;
		}
		
		public void FalsifyFilesExistAtForLocation(MNFSObjectLocation location) {
			foreach (KeyValuePair<string, MNSynchronisedFile> kvp in _files) {
				kvp.Value.SetExistsAtForLocation(location, false);
			}
		}
		
		public void FalsifyDirectoriesExistAtForLocation(MNFSObjectLocation location) {
			foreach (KeyValuePair<string, MNSynchronisedDirectory> kvp in _directories) {
				kvp.Value.SetExistsAtForLocation(location, false);
			}
		}
		
		/// <summary>
		/// Remove files from the tree that no longer exist at any location 
		/// </summary>
		/// <returns>
		/// A <see cref="System.Boolean"/> true if any file were removed.
		/// </returns>
		public bool RemoveUnnecessaryFiles() {
			List<string> removeList = new List<string>();
			foreach (KeyValuePair<string, MNSynchronisedFile> kvp in _files) {
				if(!kvp.Value.GetExistsAtAnyLocation() || this.Ignore || this.Private)	{
					removeList.Add(kvp.Key);
				}
			}	

			foreach (string item in removeList) {
				_files.Remove(item);
			}
			
			return (removeList.Count > 0);
		}
		
		/// <summary>
		/// Remove child directories that don't exists anywhere 
		/// </summary>
		/// <returns>
		/// A <see cref="System.Boolean"/> true if any directories were removed.
		/// </returns>
		public bool RemoveUnnecessaryDirectories() {
			List<string> removeList = new List<string>();
			foreach (KeyValuePair<string, MNSynchronisedDirectory> kvp in _directories) {
				if(!kvp.Value.GetExistsAtAnyLocation() || this.Ignore || this.Private)	{
					removeList.Add(kvp.Key);
				}
			}	

			foreach (string item in removeList) {
				_directories.Remove(item);
			}	
			
			return (removeList.Count > 0);
		}			
		
		#endregion
		
		#region events 
		//raising

		protected void SubscribeChildUpdate(ChildUpdateEvent eventHandler) {
			RaiseChildUpdateEvent += eventHandler;
		}
		
		protected void UnsubscribeChildUpdate(ChildUpdateEvent eventHandler) {
			RaiseChildUpdateEvent -= eventHandler;
		}	
		
		private void DoRaiseChildUpdateEvent(MNChildUpdateEventArgs e) {
			if(RaiseChildUpdateEvent != null)
				RaiseChildUpdateEvent(e);			
		}
		
		private void DoRaiseChildUpdateEvent(MNFSObjectLocation location, DateTime newChildFileFound) {
			if(RaiseChildUpdateEvent != null) {
				MNChildUpdateEventArgs e = new MNChildUpdateEventArgs(location, newChildFileFound);
				RaiseChildUpdateEvent(e);			
			}
		}
		
		// handling

		protected virtual void OnChildUpdateEvent(MNChildUpdateEventArgs e) {
			if(e.NewFind) {
				_dateFound[(int)e.Location] = e.Found;
			}
			
			//cascade up, let this directories parent know of the event.
			DoRaiseChildUpdateEvent(e);
		}
		
		#endregion
	}
}
